#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>

#include <sys/types.h>
#include <sys/param.h>
#include <ufs/ffs/fs.h>
#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>

static int open_disk(const char *);
static char * read_inodes(int, ino_t);
static void print_dbib_range(int *, int, int);
static void print_inode(int, int *, int *, int);
static void usage(void);


#define		DEVICEADDR	0
/* # of ib[0] in block for UFS1 */
#define		NINDIRS		afs.fs_bsize/sizeof(int32_t)	


union {
    struct fs fs; 
    char pad[MAXBSIZE];
} fsun;
#define afs fsun.fs


extern char *__progname;

/* dumpfs routine */
static int
open_disk(const char *name)
{
    int fd, i, sbtry[] = SBLOCKSEARCH;
    ssize_t n;

    if ((fd = open(name, O_RDONLY, 0)) < 0) {
        warn("%s", name);
        return -1;
    }

    /* Read superblock, could be UFS1 or UFS2. */
    for (i = 0; sbtry[i] != -1; i++) {
        n = pread(fd, &afs, SBLOCKSIZE, (off_t)sbtry[i]);
        if (n == SBLOCKSIZE && (afs.fs_magic == FS_UFS1_MAGIC ||
            (afs.fs_magic == FS_UFS2_MAGIC &&
            afs.fs_sblockloc == sbtry[i])) &&
            afs.fs_bsize <= MAXBSIZE &&
            afs.fs_bsize >= sizeof(struct fs))
            break;
    }
    
    if (sbtry[i] == -1) {
        warnx("Can not find filesystem superblock");
        close(fd);
        return -1;
    }

    return fd;
}

/* 
 * Get block of inodes with our inode
 */
static char *
read_inodes(int fd, ino_t inum)
{
	off_t offset;
	char *dp;
	
	/* Read in the appropriate block. */
	offset = ino_to_fsba(&afs, inum);	/* inode to fs blk */
	offset = fsbtodb(&afs, offset);		/* fs blk disk blk */
	offset *= DEV_BSIZE;				/* disk blk to bytes */	
	
	dp = malloc(MAXBSIZE);
	if (dp == NULL) {
		close(fd);
		errx(1, "Malloc error");
	}
	
	if (pread (fd, dp, afs.fs_bsize, offset) != afs.fs_bsize) {
		close(fd);
		errx(1, "Can not read inode");
	}
	
	return dp;
}

/* 
 * Print cg group of data, block and size
 */
static void
print_dbib_range(int *pb, int max, int blocks)
{
	int i, j;
	size_t size;
	int	bpsize = (afs.fs_bsize / DEV_BSIZE); /* Size of fs block in disk blocks */
	
	
	for (i = 0; i < max && pb[i] != 0; i++) {
		/* CG group */
		j = 0;
		while (!(j < afs.fs_ncg && pb[i] >= cgdmin(&afs,j) && pb[i] < cgdmin(&afs,j+1)))
			j++;
			
		printf(" %4d   ", j);
		
		
		/* FS blocks */
		printf("%7d ", pb[i]);
		
		j = i;
		size = 1;
		while ((i+1) < max && ((pb[i]+8) == pb[i+1]))
			i++;
			
		if (i != j) {
			printf("- %d", pb[i]); 
			size += i - j;
		}

#if DEVICEADDR
		/* Device addr */
		printf("\t");
		printf("%15d", fsbtodb(&afs, pb[j]) * DEV_BSIZE);
		
		if (end != -1)
			printf(" - %d", fsbtodb(&afs, pb[i]) * DEV_BSIZE);
		else
			printf(" - %d", fsbtodb(&afs, pb[j]) * DEV_BSIZE);
		
#endif	
	
		/* Last piece might be fragment - recount size */
		if (blocks >= bpsize)
			size *= afs.fs_bsize;
		else
			size = blocks * DEV_BSIZE;
			
		blocks -= bpsize;
		
		printf("\t");	
		printf("%17lu\n", (unsigned long) size);
	}
}	

/*
 * Go throw direct and 1st, 2nd, 3rd indirect blocks
 */
static void
print_inode(int fd, int *db, int *ib, int blocks)
{
	int i,j;
	int ib1[NINDIRS];
	int ib2[NINDIRS];
	int ib3[NINDIRS];

#if DEVICEADDR
	printf("Cyl grp     FS block \t\t    Dev addr \t\t\t  Size\n");
	printf("-------   ------------ \t\t  ------------ \t\t\t--------\n");
#else
	printf("Cyl grp     FS block \t\t   Size\n");
	printf("-------   ------------ \t\t --------\n");
#endif


	/* Direct block */
	print_dbib_range(db, NDADDR, blocks);
	
	/* 1st indirect block */
	if (ib[0] != 0) {
		pread(fd, &ib1, sizeof(ib1), fsbtodb(&afs, ib[0]) * DEV_BSIZE);
		print_dbib_range(ib1, NINDIRS, blocks);		
	}
	
	
	/* 2nd indirect block */
	if (ib[1] != 0) {			
		if (pread(fd, &ib1, sizeof(ib1), fsbtodb(&afs, ib[1]) * DEV_BSIZE) != sizeof(ib1))
			warn("Can not read 2nd indirect block");
		j=0;
		while (ib1[j] != 0) {
			if (pread(fd, &ib2, sizeof(ib2), fsbtodb(&afs, ib1[j]) * DEV_BSIZE) != sizeof(ib2))
				warn("Can not read 2nd indirect block");
			print_dbib_range(ib2, NINDIRS, blocks);	
			j++;
		}
	}
	
	/* 3rd indirect block is used */
	if (ib[2] != 0) {	
		/* read 1st */
		if (pread(fd, &ib1, sizeof(ib1), fsbtodb(&afs, ib[2]) * DEV_BSIZE) != sizeof(ib1))
			warn("Can not read 3rd indirect block");
			
		j=0;
		while (ib1[j] != 0) {
			/* read 2nd */
			if (pread(fd, &ib2, sizeof(ib2), fsbtodb(&afs, ib1[j]) * DEV_BSIZE) != sizeof(ib2))
				warn("Can not read 3rd indirect block");
			
			i=0;
			while (ib2[i] != 0) {
				/* read 3rd */
				if (pread(fd, &ib3, sizeof(ib3), fsbtodb(&afs, ib2[i]) * DEV_BSIZE) != sizeof(ib3))
					warn("Can not read 3rd indirect block");
					
				print_dbib_range(ib3, NINDIRS, blocks);	
				i++;
			}
			
			j++;
		}
	}

}


static void
usage()
{
	fprintf(stderr, "Usage: %s inode_number filesystem\n", __progname);
	exit(1);
}

int 
main(int argc, char *argv[])
{
	int fd;
	ino_t imax;
	ino_t inum;
	const char *errstr;
	char *dp;
	struct ufs1_dinode *dp1;
	
	
	if (argc != 3) {
		usage();
	}

	if ((fd = open_disk(argv[2])) == -1)
		return 1;
		
	
	imax = afs.fs_ncg * afs.fs_ipg;
	inum = (ino_t) strtonum(argv[1], 2, imax, &errstr);
	
	if (errstr)
		errx(1, "Inode number (%s) is %s", argv[1], errstr);
		
	dp = read_inodes(fd, inum);
	
	if (afs.fs_magic == FS_UFS1_MAGIC) {
		dp1 = &(((struct ufs1_dinode *)dp)[ino_to_fsbo(&afs, inum)]);
		print_inode(fd, dp1->di_db, dp1->di_ib, dp1->di_blocks);
	}
	else {
		errx(1, "UFS2 is not supported yet");
	}

	free(dp);
	close(fd);

	return 0;
}
